/*
 * @Date: 2022-05-23 08:21:27
 * @LastEditors: sailornpg
 * @LastEditTime: 2022-05-23 11:30:30
 * @FilePath: \my-react-components\src\component\AutoComplete\index.tsx
 * @Description: AutoComplete
 */
import React, {
  ChangeEvent,
  memo,
  useEffect,
  useState,
  KeyboardEvent,
  useRef,
} from "react";
import Input, { IInputProps } from "@/component/Input";
import useDebounce from "@/hooks/useDebounce";
import classNames from "classnames";
import useClickOutSide from "@/hooks/useClickOutSide";
interface DataSource {
  value: string;
}
export type DataSourceType<T = {}> = T & DataSource;
export interface AutoCompleteProp extends Omit<IInputProps, "onSelect"> {
  onSelect?: (item: DataSourceType) => void;
  fetchSuggestions: (
    keyword: string,
  ) => DataSourceType[] | Promise<DataSourceType[]>;
  renderOption?: (item: DataSourceType) => React.ReactElement;
}
const AutoComplete: React.FC<AutoCompleteProp> = memo(
  ({ fetchSuggestions, onSelect, value, renderOption, ...restProp }) => {
    const [inpVal, setInpVal] = useState(value as string);
    const [Suggestions, setSuggestions] = useState<DataSourceType[]>([]);
    const [highlightIndex, setHighlightIndex] = useState(-1);
    const debounceVal = useDebounce(inpVal);
    const componentRef = useRef<HTMLDivElement>(null);
    useClickOutSide(componentRef, () => {
      setSuggestions([]);
    });
    useEffect(() => {
      if (debounceVal) {
        const value = fetchSuggestions(inpVal);
        if (value instanceof Promise) {
          value.then((res) => {
            setSuggestions(res);
          });
        } else {
          setSuggestions(value);
        }
      } else {
        setSuggestions([]);
      }
    }, [debounceVal]);
    const handleChangee = (e: ChangeEvent<HTMLInputElement>) => {
      const val = e.target.value.trim();
      setInpVal(val);
    };
    const handleSelect = (item: DataSourceType) => {
      setInpVal(item.value);
      setSuggestions([]);
      if (onSelect) {
        onSelect(item);
      }
    };
    const renderTmp = (item: DataSourceType) => {
      return renderOption ? renderOption(item) : item.value;
    };
    const generateDrowdown = () => {
      return (
        <ul className="sailor-suggestion-list">
          {Suggestions.map((item, index) => {
            const classnames = classNames("suggestion-item", {
              "is-active": index === highlightIndex,
            });
            return (
              <li
                key={index}
                onClick={(e) => handleSelect(item)}
                className={classnames}
              >
                {renderTmp(item)}
              </li>
            );
          })}
        </ul>
      );
    };
    const highlight = (index: number) => {
      if (index < 0) index = 0;
      if (index >= Suggestions.length) {
        index = Suggestions.length - 1;
      }
      setHighlightIndex(index);
    };
    const keyDown = (e: KeyboardEvent<HTMLInputElement>) => {
      switch (e.key) {
        case "Escape":
          setSuggestions([]);
          break;
        case "ArrowUp":
          highlight(highlightIndex - 1);
          break;
        case "ArrowDown":
          highlight(highlightIndex + 1);
          break;
        case "Enter":
          if (Suggestions[highlightIndex]) {
            handleSelect(Suggestions[highlightIndex]);
          }
          break;
        default:
          break;
      }
    };
    return (
      <div className="sailor-auto-complete" ref={componentRef}>
        <Input
          value={inpVal}
          onChange={handleChangee}
          {...restProp}
          onKeyDown={keyDown}
        ></Input>
        {Suggestions.length > 0 && generateDrowdown()}
      </div>
    );
  },
);

export default AutoComplete;
